home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 4
/
Apprentice-Release4.iso
/
Source Code
/
C
/
Frameworks
/
Grant's CGI Framework 1.0b12
/
Util
/
CGI.c
next >
Wrap
Text File
|
1995-12-09
|
38KB
|
1,371 lines
/*****
*
* Grant's CGI Framework
* (Common Grant Interface :-)
* by Grant Neufeld
* http://arpp1.carleton.ca/grant/mac/grantscgi.html
*
* CGI.c
*
* Standard functions for cgi applications.
*
* You must call InitCGIUtil in your application startup.
* You must install CGIAEHandle as the event handler for the WWWΩsdoc apple event
* You must write the function:
* void MyCGIProcess ( CGIHdl theCGIHdl )
* which is where you will, guess what, do your application specific processing
* of the cgi stuff.
*
* Do not call any functions begining with lower case 'cgi' - you can use any of the
* others - but read their comments first for details.
*
* watch the homepage for future upgrades
*
* notice of upgrades will be posted to macwwwtool@arpp1.carelton.ca
* see http://arpp1.carleton.ca/list/macwwwtool.html for details
*
*
* Copyright ©1995 by Grant Neufeld
*
* http://arpp1.carleton.ca/grant/
* gneufeld@ccs.carleton.ca
* grant@acm.org
*
* This source may be freely used as long as the copyright notice is kept in the source.
* I ask that you let me know of any enhancements (read: bug fixes) to this code.
* I would also like copies of (or discounts on) anything you produce this with, please.
*
* See the License and Limited Warranty Agreement for all the legal stuff.
*
*****/
#include "MyConfiguration.h"
#if kCompileWithCGICode
#include <string.h>
#include "compiler_stuff.h"
#include "globals.h"
#include "AEFunc.h"
#include "DebugUtil.h"
#include "MemoryUtil.h"
#include "ProcessUtil.h"
#include "Quit.h"
#include "StringUtil.h"
/* CGI.h processes differently for CGI.c, this is controlled by defining __CGISegment__ */
#define __CGISegment__ 1
#include "CGI.h"
#undef __CGISegment__
/*** CONSTANT DECLARATIONS ***/
#define kHTTPHeaderStrs 3000
#define kHTTPHeaderOK 1
#define kHTTPHeaderRedirect 2
#define kHTTPHeaderErr 3
#define kHTTPHeaderPush 4
/*** LOCAL VARIABLES ***/
static AEEventHandlerUPP vCGIAEResumeCompleteUPP;
/*** LOCAL FUNCTION PROTOTYPES ***/
static void cgiDisposeHandle ( CGIHdl );
pascal void * CGIAESearchDocProcessThread ( void * );
static OSErr cgiAESearchDocProcess ( CGIHdl );
static OSErr cgiAEComplete ( CGIHdl );
pascal OSErr CGIAEResumeComplete ( const AppleEvent *, AppleEvent *, long );
#if kCompileWithCGImethod
static OSErr cgiAEGetParamHTTPMethod ( const AppleEvent *, AEKeyword, HTTPMethod *, char *, long );
#endif
/*** FUNCTIONS ***/
/* This initialization function MUST be called in the startup sequence of your application */
OSErr
InitCGIUtil ( void )
{
OSErr theErr;
AEEventHandlerUPP theUPP;
GetIndString ( gHTTPHeaderOK, kHTTPHeaderStrs, kHTTPHeaderOK );
if ( gHTTPHeaderOK != nil )
{
P2CStr ( gHTTPHeaderOK );
gHTTPHeaderOKSize = strlen ( (char *)gHTTPHeaderOK );
}
GetIndString ( gHTTPHeaderRedirect, kHTTPHeaderStrs, kHTTPHeaderRedirect );
if ( gHTTPHeaderRedirect != nil )
{
P2CStr ( gHTTPHeaderRedirect );
gHTTPHeaderRedirectSize = strlen ( (char *)gHTTPHeaderRedirect );
}
GetIndString ( gHTTPHeaderErr, kHTTPHeaderStrs, kHTTPHeaderErr );
if ( gHTTPHeaderErr != nil )
{
P2CStr ( gHTTPHeaderErr );
gHTTPHeaderErrSize = strlen ( (char *)gHTTPHeaderErr );
}
#if kCompileWithCGISendPartial
GetIndString ( gHTTPHeaderPush, kHTTPHeaderStrs, kHTTPHeaderPush );
if ( gHTTPHeaderPush != nil )
{
P2CStr ( gHTTPHeaderPush );
gHTTPHeaderPushSize = strlen ( (char *)gHTTPHeaderPush );
}
#endif
theUPP = NewAEEventHandlerProc ( CGIAESearchDoc );
theErr = AEInstallEventHandler ( kAEClassCGI, kAEIDSearchDoc, theUPP, 0L, false );
vCGIAEResumeCompleteUPP = NewAEEventHandlerProc ( CGIAEResumeComplete );
return theErr;
} /* InitCGIUtil */
/** FORM FIELDS **/
#pragma mark -
#if kCompileWithCGIFormHandling
/* The separator '&' separates individual fields.
The delimiter '=' delimits the name and value in a field.
For example: "Field 1=some stuff&Another Field=more stuff&Last Field=no stuff"
Means that there are 3 fields with names "Field 1", "Another Field" and "Last Field"
The function returns an array of field records with the last containing null values.
You generally shouldn't call this function outside this file.
Use it at your own risk. */
/* ••• I should add error reporting */
CGIFormField *
CGIFormFieldsFromArgs ( char *theString, long *count, short *outErr )
{
CGIFormField * theFields;
long totalStrSize;
long totalFields;
long nameSize;
long valueSize;
long currentField;
char * theStringPtr;
char * fieldSeparator;
char * fieldDelimiter;
my_assert ( theString != nil, "\pCGIFormArgs: nil string" );
theFields = nil;
/* don't return number of fields until function is successful */
*count = nil;
totalStrSize = strlen ( theString );
/* the total number of fields is the number of separator characters + 1 */
totalFields = StringCountChar ( theString, kCGIFormFieldSeparator ) + 1;
if ( totalFields == 1 )
{
/* the case where there were no separator characters is special,
test for a field delimiter to confirm that the string passed does
indeed contain field information */
fieldDelimiter = StringChar ( theString, kCGIFormFieldDelimiter );
if ( fieldDelimiter == nil )
{
/* string does not contain field data */
*outErr = 1;
goto Exit_Fail;
}
}
theFields = (CGIFormField *) MyNewPtr ( ((totalFields + 1) * sizeof(CGIFormField)), outErr );
if ( theFields == nil )
{
/* memory didn't allocate */
*outErr = memFullErr;
goto Exit_Fail;
}
/* set the name and value of the last field in the array to nil */
(theFields[totalFields]).name = nil;
(theFields[totalFields]).value = nil;
theStringPtr = theString;
for ( currentField = nil; currentField < totalFields; currentField++ )
{
#if kCompileWithThreadsOptional
if ( gHasThreadMgr )
#endif
{
YieldToAnyThread ();
}
/* set the name and value of the current field in the array to nil.
this is to handle errors. */
(theFields[currentField]).name = nil;
(theFields[currentField]).value = nil;
fieldDelimiter = StringChar ( theStringPtr, kCGIFormFieldDelimiter );
fieldSeparator = StringChar ( theStringPtr, kCGIFormFieldSeparator );
/* if there is a field delimiter, and it is before any field separator */
if ( (fieldDelimiter != nil) && ((fieldSeparator > fieldDelimiter) || (fieldSeparator == nil)) )
{
/* field name */
/* the size of the name string is the difference between the begining of the
field and the position of the field delimiter */
nameSize = fieldDelimiter - theStringPtr;
/* allocate the name string */
(theFields[currentField]).name = (char *) MyNewPtr ( nameSize + 1, outErr );
if ( (theFields[currentField]).name == nil )
{
/* memory didn't allocate */
*outErr = memFullErr;
(theFields[currentField]).value = nil;
goto Exit_Fail;
}
/* copy the field name */
BlockMove ( theStringPtr, (theFields[currentField]).name, nameSize );
/* null terminate the end of the name string */
((theFields[currentField]).name)[nameSize] = nil;
/* convert the url encoded text to a normal string */
CGIDecodeSpecialChars ( (theFields[currentField]).name );
/* field value */
if ( fieldSeparator != nil )
{
valueSize = fieldSeparator - (fieldDelimiter + 1);
}
else
{
valueSize = strlen ( fieldDelimiter + 1 );
}
(theFields[currentField]).value = (char *) MyNewPtr ( (valueSize + 1), outErr );
if ( (theFields[currentField]).value == nil )
{
/* memory didn't allocate */
*outErr = memFullErr;
DisposePtr ( (theFields[currentField]).name );
(theFields[currentField]).name = nil;
goto Exit_Fail;
}
BlockMove ( fieldDelimiter + 1, (theFields[currentField]).value, valueSize );
((theFields[currentField]).value)[valueSize] = nil;
CGIDecodeSpecialChars ( (theFields[currentField]).value );
theStringPtr = fieldSeparator + 1;
}
else
{
/* invalid data encountered */
*outErr = 2;
goto Exit_Fail;
}
}
/* assign the return parameters values */
*count = totalFields;
*outErr = noErr;
return theFields;
Exit_Fail:
if ( theFields != nil )
{
/* release allocated memory */
CGIFormFieldsDispose ( theFields );
}
return nil;
} /* CGIFormFieldsFromArgs */
/* Returns a pointer to the first form field record, in the given fieldArray,
that's name matches the supplied field name.
[modified for 1.0b4 - use CGIHdl as parameter instead of CGIFormField] */
CGIFormField *
CGIFormFieldsFindRecord ( CGIHdl theCGIHdl, char *fieldName )
{
CGIFormField *fieldArray;
long currentField;
fieldArray = (*theCGIHdl)->formFields;
/* look til we find something or we hit the end */
for ( currentField = nil; (fieldArray[currentField]).name != nil; currentField++ )
{
#if kCompileWithThreadsOptional
if ( gHasThreadMgr )
#endif
{
YieldToAnyThread ();
}
if ( strcmp((fieldArray[currentField]).name, fieldName) == nil )
{
/* found a match, so we're done */
return &(fieldArray[currentField]);
}
}
/* didn't find a match */
return nil;
} /* CGIFormFieldsFindRecord */
/* Deallocate memory for theFields array.
You generally shouldn't call this function outside this file.
Use it at your own risk. */
void
CGIFormFieldsDispose ( CGIFormField *theFields )
{
long offset;
my_assert ( theFields != nil, "\pCGIFormFieldsDispose: null field array pointer" );
offset = nil;
do
{
#if kCompileWithThreadsOptional
if ( gHasThreadMgr )
#endif
{
YieldToAnyThread ();
}
if ( (theFields[offset]).name != nil )
{
/* if there's a name string, deallocate its memory */
DisposePtr ( (Ptr)((theFields[offset]).name) );
if ( (theFields[offset]).value != nil )
{
/* if there's a value string, deallocate its memory */
DisposePtr ( (Ptr)((theFields[offset]).value) );
}
}
offset++;
} while ( (theFields[offset]).name != nil );
DisposePtr ( (Ptr)theFields );
} /* CGIFormFieldsDispose */
#endif /* kCompileWithCGIFormHandling */
/** CHARACTER CODING **/
#pragma mark -
/* replaces instances of percent signs (%) followed by an ASCII char value
with the actual character.
This function modifies theString parameter! */
void
CGIDecodeSpecialChars ( char *theString )
{
int read;
int write;
unsigned char theChar;
unsigned char highOrder;
unsigned char lowOrder;
Boolean isValid;
my_assert ( theString != nil, "\pCGIDecodeSpecialChars: nil string" );
read = nil;
write = nil;
while ( theString[read] != nil )
{
switch ( theString[read] )
{
case '%':
/* a percent symbol begins a hex char block (%## where ## is the hex value) */
isValid = true;
/* determine high order hex character */
if ( (theString[read+1] >= 'A') && (theString[read+1] <= 'F') )
{
/* uppercase A-F convert to 10-15 */
highOrder = theString[read+1] - 'A' + 10;
}
else if ( (theString[read+1] >= 'a') && (theString[read+1] <= 'f') )
{
/* lowercase a-f convert to 10-15 */
highOrder = theString[read+1] - 'a' + 10;
}
else if ( (theString[read+1] >= '0') && (theString[read+1] <= '9') )
{
/* character digits 0-9 convert to decimal 0-9 */
highOrder = theString[read+1] - '0';
}
else
{
/* Illegal character! Can't convert from hex */
isValid = false;
}
/* Multiply high order hex digit by 16 */
highOrder *= 16;
/* determine low order hex character */
if ( (theString[read+2] >= 'A') && (theString[read+2] <= 'F') )
{
/* uppercase A-F convert to 10-15 */
lowOrder = (theString[read+2] - 'A' + 10);
}
else if ( (theString[read+2] >= 'a') && (theString[read+2] <= 'f') )
{
/* lowercase a-f convert to 10-15 */
lowOrder = (theString[read+2] - 'a' + 10);
}
else if ( (theString[read+2] >= '0') && (theString[read+2] <= '9') )
{
/* character digits 0-9 convert to decimal 0-9 */
lowOrder = (theString[read+2] - '0');
}
else
{
/* Illegal character! Can't convert from hex */
isValid = false;
}
theChar = highOrder + lowOrder;
if ( isValid )
{
isValid = (theChar >= 0) && (theChar < 256);
}
if ( isValid )
{
/* if theChar is valid, write it out */
if ( theChar == 10 )
{
/* don't write newline */
write--;
}
else
{
theString[write] = theChar;
}
/* Increment read past the two digits of the hex code */
read += 2;
}
else
{
/* invalid hex character code, just write out the percent symbol */
theString[write] = theString[read];
}
break;
case '+':
/* Plus symbols convert to space */
theString[write] = ' ';
break;
case 10:
/* ignore line feeds, we only need carriage returns (13) */
write--;
break;
default:
/* write out the character */
theString[write] = theString[read];
break;
}
read++;
write++;
}
/* terminate the string */
theString[write] = '\0';
} /* CGIDecodeSpecialChars */
#define kCGIEncodeExtraChars 16
/* %hex encode all non-alphanumeric characters
theString parameter is not modified */
char *
CGIEncodeSpecialChars ( char *theString )
{
char * theResult;
long strSize; /* size of the source string */
long tempSize; /* current size of the result */
long strOffset; /* pointer to current read position in the source string */
long resultOffset; /* pointer to current write position in the result string */
strSize = strlen ( theString );
tempSize = strSize + kCGIEncodeExtraChars;
theResult = (char *) MyNewPtr ( (tempSize + 1), nil );
for ( strOffset = resultOffset = nil; strOffset < strSize; strOffset++, resultOffset++ )
{
if ( resultOffset > tempSize )
{
/* the current size of the result string isn't big enough, so grow it */
/* ••• check if there may be errors if the grow fails */
tempSize += kCGIEncodeExtraChars;
SetPtrSize ( (Ptr)theResult, tempSize + 1 );
}
if ( ((theString[strOffset] >= '0') && (theString[strOffset] <= '9')) ||
((theString[strOffset] >= 'A') && (theString[strOffset] <= 'Z')) ||
((theString[strOffset] >= 'a') && (theString[strOffset] <= 'z')) )
{
/* if the character is alphanumeric just copy it */
theResult[resultOffset] = theString[strOffset];
}
else
{
/* if the character is not alphanumeric, hex encode it */
if ( (resultOffset + 2) > tempSize )
{
#if kCompileWithThreadsOptional
if ( gHasThreadMgr )
#endif
{
/* this loop should be quite fast, but we'll yield if we
have to start growing the block of working memory (theResult) */
YieldToAnyThread ();
}
/* the current size of the result string isn't big enough, so grow it */
tempSize += kCGIEncodeExtraChars;
SetPtrSize ( (Ptr)theResult, tempSize + 1 );
}
/* hex encode character */
CGICharToHex ( theString[strOffset], &(theResult[resultOffset]) );
/* add the extra two characters for hex encoding to the result offset */
resultOffset += 2;
}
}
/* increment the offset to accomodate the string terminator */
resultOffset++;
if ( resultOffset > tempSize )
{
/* the current size of the result string isn't big enough, so grow it */
tempSize += kCGIEncodeExtraChars;
SetPtrSize ( (Ptr)theResult, tempSize + 1 );
}
/* terminate the string */
theResult[resultOffset] = nil;
if ( resultOffset != tempSize )
{
/* shrink the memory for the result string to exactly match it's needs */
SetPtrSize ( (Ptr)theResult, resultOffset + 1 );
}
return theResult;
} /* CGIEncodeSpecialChars */
/* Converts a character to percent-hex encoding and writes that to the string.
This assumes that the string is at least 4 bytes long. */
void
CGICharToHex ( unsigned char theChar, char *theString )
{
unsigned char chrChunk;
/* start off with a percent symbol to indicate a hex character code */
theString[0] = '%';
/* mask to get the high 4 bits, then shift them into the lower 4 bits */
chrChunk = ( theChar & 0xF0 ) >> 4;
if ( chrChunk > 9 )
{
/* chrChunk is a value between A and F in hex */
theString[1] = ( chrChunk - 10 ) + 'A';
}
else
{
/* chrChunk is a value between 0 and 9 in hex */
theString[1] = chrChunk + '0';
}
/* mask to get the low 4 bits */
chrChunk = theChar & 0x0F;
if ( chrChunk > 9 )
{
/* chrChunk is a value between A and F in hex */
theString[2] = ( chrChunk - 10 ) + 'A';
}
else
{
/* chrChunk is a value between 0 and 9 in hex */
theString[2] = chrChunk + '0';
}
} /* CGICharToHex */
/** Memory Allocation Cleanup **/
#pragma mark -
/* */
static void
cgiDisposeHandle ( CGIHdl theCGIHdl )
{
my_assert ( theCGIHdl != nil, "\pcgiDisposeHandle: theCGIHdl is nil" );
/* the following is a bunch of statements checking if a parameter has been
allocated and disposing of it if it has */
#if kCompileWithCGIpath_args
if ( (*theCGIHdl)->path_args != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->path_args) );
}
#endif
#if kCompileWithCGIhttp_search_args
if ( (*theCGIHdl)->http_search_args != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->http_search_args) );
}
#endif
#if kCompileWithCGIfrom_user
if ( (*theCGIHdl)->from_user != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->from_user) );
}
#endif
#if kCompileWithCGIclient_address
if ( (*theCGIHdl)->client_address != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->client_address) );
}
#endif
#if kCompileWithCGIpost_args
if ( (*theCGIHdl)->post_args != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->post_args) );
}
#endif
#if kCompileWithCGIserver_name
if ( (*theCGIHdl)->server_name != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->server_name) );
}
#endif
#if kCompileWithCGIscript_name
if ( (*theCGIHdl)->script_name != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->script_name) );
}
#endif
#if kCompileWithCGIcontent_type
if ( (*theCGIHdl)->content_type != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->content_type) );
}
#endif
#if kCompileWithCGIreferer
if ( (*theCGIHdl)->referer != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->referer) );
}
#endif
#if kCompileWithCGIuser_agent
if ( (*theCGIHdl)->user_agent != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->user_agent) );
}
#endif
#if kCompileWithCGIActionSupport
if ( (*theCGIHdl)->action_path != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->action_path) );
}
#endif
#if kCompileWithCGIfull_request
if ( (*theCGIHdl)->full_request != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->full_request) );
}
#endif
#if kCompileWithCGIversion
if ( (*theCGIHdl)->version != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->version) );
}
#endif
#if kCompileWithCGIFormHandling
if ( (*theCGIHdl)->formFields != nil )
{
CGIFormFieldsDispose ( (*theCGIHdl)->formFields );
}
#endif
if ( (*theCGIHdl)->responseData != nil )
{
DisposePtr ( (Ptr)((*theCGIHdl)->responseData) );
}
DisposeHandle ( (Handle)theCGIHdl );
} /* cgiDisposeHandle */
/** APPLE EVENT SUPPORT **/
#pragma mark -
/* AppleEvent Handler for the CGI WWWΩ-sdoc event */
pascal OSErr
CGIAESearchDoc ( AppleEvent *theAppleEvent, AppleEvent *theReply, long Reference )
{
OSErr theErr;
CGIHdl theCGIHdl;
ThreadID theThread;
/* reset 'quit on idle time' timer */
ResetQuitIdleTimer();
theCGIHdl = (CGIHdl) MyNewHandleClear ( sizeof(CGIrecord), &theErr );
if ( theCGIHdl == nil )
{
/* memory didn't allocate - can't process cgi */
return theErr;
}
/* store references to the apple event and reply records */
(*theCGIHdl)->appleEvent = *theAppleEvent;
(*theCGIHdl)->replyEvent = *theReply;
// MoveHHi ( (Handle)theCGIHdl );
#if kCompileWithThreadsOptional
if ( gHasThreadMgr )
{
#endif
/* It is necessary to suspend the AppleEvent in order to thread its
processing because of some real weirdness with AEProcessAppleEvent
not being "reentrant." This means you can't be processing multiple
Apple Events at the same time, so they have to be 'suspended' if
you want to deal with more than one (IE. multi-threaded processing.) */
theErr = AESuspendTheCurrentEvent ( theAppleEvent );
if ( theErr == noErr)
{
/* AppleEvent has been suspended, so we can spawn a thread for processing */
theErr = MyNewThreadFromPool ( CGIAESearchDocProcessThread, theCGIHdl, (void**)nil, &theThread );
}
#if kCompileWithThreadsOptional
}
#endif
#if kCompileWithThreadsOptional
if ( !gHasThreadMgr || (theErr != noErr) )
#else
if ( theErr != noErr )
#endif
{
/* if threading isn't available, or the attempt to thread failed,
or the attempt to suspend the AppleEvent failed,
process the Apple Event without threading */
theErr = cgiAESearchDocProcess ( theCGIHdl );
}
return theErr;
} /* CGIAESearchDoc */
/* entry point for cgi handler thread */
pascal void *
CGIAESearchDocProcessThread ( void *threadParam )
{
OSErr theErr;
CGIHdl theCGIHdl;
ThreadID currentThread;
my_assert ( threadParam != nil, "\pCGIAESearchDocProcessThread: the CGI handle (threadParam) is nil" );
theCGIHdl = (CGIHdl)threadParam;
GetCurrentThread ( ¤tThread );
(*theCGIHdl)->thread = currentThread;
theErr = cgiAESearchDocProcess ( theCGIHdl );
DisposeThread ( currentThread, (void *)theErr, true );
return (void *)theErr;
} /* CGIAESearchDocProcessThread */
/* Process the CGI WWWΩ-sdoc event.
theReference must be the CGI handle, which must be unlocked. */
static OSErr
cgiAESearchDocProcess ( CGIHdl theCGIHdl )
{
OSErr theErr;
Ptr tempBuffer;
AppleEvent theAppleEvent;
// AppleEvent theReply;
/* the fieldError variable is only used if forms with auto-processing are on,
the method parameter is used, and one or both of the http_search_args and
post_args are used */
#if kCompileWithCGIFormHandling && kCompileWithCGIFormAutoProcess && kCompileWithCGImethod && (kCompileWithCGIhttp_search_args || kCompileWithCGIpost_args)
short fieldError;
#endif
my_assert ( theCGIHdl != nil, "\pcgiAESearchDocProcess: theCGIHdl is nil" );
// my_assert ( (*theCGIHdl)->appleEvent != nil, "\pcgiAESearchDocProcess: theAppleEvent is nil" );
/* copy the AppleEvent record pointer into a local variable for faster access */
theAppleEvent = (*theCGIHdl)->appleEvent;
/* reset 'quit on idle time' timer */
ResetQuitIdleTimer();
tempBuffer = MyNewPtr ( kCGIParamMaxSize, &theErr );
if ( tempBuffer == nil )
{
goto Exit_Complete;
}
HLockHi ( (Handle)theCGIHdl );
/* the following section is where the parameters are pulled from the CGI
Apple Event and allocated in the CGI Handle */
/* '----' - direct parameter:
path_args - arguments to the URL after a $ */
#if kCompileWithCGIpath_args
theErr = AEGetParamString ( &theAppleEvent, '----', &((*theCGIHdl)->path_args),
(char *)tempBuffer, kCGIParamMaxSize );
#if kCompileWithCGIAutoDecode
if ( theErr == noErr )
{
CGIDecodeSpecialChars ( (*theCGIHdl)->path_args );
}
#endif
#endif /* kCompileWithCGIpath_args */
/* 'kfor' - search arguments:
http_search_args - arguments to the URL after a ? */
#if kCompileWithCGIhttp_search_args
theErr = AEGetParamString ( &theAppleEvent, kCGIhttp_search_args, &((*theCGIHdl)->http_search_args),
(char *)tempBuffer, kCGIParamMaxSize );
/* leave decoding to after parsing of form fields */
#endif
/* 'user' - user name:
username - authenticated user name */
#if kCompileWithCGIusername
theErr = AEGetParamStringNoAlloc (
&theAppleEvent, kCGIusername, (*theCGIHdl)->username, kCGIMaxusername );
#endif
/* 'pass' - password:
password - authenticated password */
#if kCompileWithCGIpassword
theErr = AEGetParamStringNoAlloc (
&theAppleEvent, kCGIpassword, (*theCGIHdl)->password, kCGIMaxpassword );
#endif
/* 'frmu' - from user:
from_user - non-standard. e-mail address of remote user */
#if kCompileWithCGIfrom_user
theErr = AEGetParamString ( &theAppleEvent, kCGIfrom_user, &((*theCGIHdl)->from_user),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'addr' - client address:
client_address - IP address or domain name of remote client's host */
#if kCompileWithCGIclient_address
theErr = AEGetParamString ( &theAppleEvent, kCGIclient_address, &((*theCGIHdl)->client_address),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'post' - post arguments:
post_args - */
#if kCompileWithCGIpost_args
theErr = AEGetParamString ( &theAppleEvent, kCGIpost_args, &((*theCGIHdl)->post_args),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'meth' - HTTP method:
method - GET, POST, etc. Used to tell if post_args are valid */
#if kCompileWithCGImethod
theErr = cgiAEGetParamHTTPMethod ( &theAppleEvent, kCGImethod, &((*theCGIHdl)->method),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'svnm' - server name:
server_name - name or IP address of this server */
#if kCompileWithCGIserver_name
theErr = AEGetParamString ( &theAppleEvent, kCGIserver_name, &((*theCGIHdl)->server_name),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'svpt' - server port:
server_port - TCP/IP port number being used by this server */
#if kCompileWithCGIserver_port
theErr = AEGetParamShort ( &theAppleEvent, kCGIserver_port, &((*theCGIHdl)->server_port),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'scnm' - script name:
script_name - URL name of this script */
#if kCompileWithCGIscript_name
theErr = AEGetParamString ( &theAppleEvent, kCGIscript_name, &((*theCGIHdl)->script_name),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'ctyp' - content type:
content_type - MIME content type of post_args */
#if kCompileWithCGIcontent_type
theErr = AEGetParamString ( &theAppleEvent, kCGIcontent_type, &((*theCGIHdl)->content_type),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'refr' - referer:
referer - the URL of the page referencing this document */
#if kCompileWithCGIreferer
theErr = AEGetParamString ( &theAppleEvent, kCGIreferer, &((*theCGIHdl)->referer),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'Agnt' - user agent:
user_agent - the name and version of the WWW client software being used */
#if kCompileWithCGIuser_agent
theErr = AEGetParamString ( &theAppleEvent, kCGIuser_agent, &((*theCGIHdl)->user_agent),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'Kact' - action name:
action - the name of the action (CGI or ACGI if not a user defined action) */
#if kCompileWithCGIActionSupport
theErr = AEGetParamStringNoAlloc (
&theAppleEvent, kCGIaction, (*theCGIHdl)->action, kCGIMaxaction );
#endif
/* 'Kapt' - action path:
action_path - path to the action application */
#if kCompileWithCGIActionSupport
theErr = AEGetParamString ( &theAppleEvent, kCGIaction_path, &((*theCGIHdl)->action_path),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'Kcip' - client IP address:
client_ip - the IP address of the client */
#if kCompileWithCGIclient_ip
theErr = AEGetParamStringNoAlloc (
&theAppleEvent, kCGIclient_ip, (*theCGIHdl)->client_ip, kCGIMaxclient_ip );
#endif
/* 'Kfrq' - full request:
full_request - the full text of the request */
#if kCompileWithCGIfull_request
theErr = AEGetParamString ( &theAppleEvent, kCGIfull_request, &((*theCGIHdl)->full_request),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'Pvrs' - version:
version - the version number of the server */
#if kCompileWithCGIversion
theErr = AEGetParamString ( &theAppleEvent, kCGIversion, &((*theCGIHdl)->version),
(char *)tempBuffer, kCGIParamMaxSize );
#endif
/* 'Kcid' - connection ID:
connection - the ID of the server's connection with a client */
#if kCompileWithCGISendPartial
theErr = AEGetParamLong ( &theAppleEvent, kCGIconnection, &((*theCGIHdl)->connection) );
#endif
/* don't need the buffer any more */
DisposePtr ( tempBuffer );
//•don't need to check for required parameters because Chuck Shotton says that all
//parameters are to be optional.
// /* check that all required parameters were retreived */
// theErr = MyGotRequiredParams ( &theAppleEvent );
#if kCompileWithCGIFormHandling && kCompileWithCGIFormAutoProcess && kCompileWithCGImethod
/* separate the form fields into an array */
switch ( (*theCGIHdl)->method )
{
#if kCompileWithCGIhttp_search_args
case HTTP_get :
if ( (*theCGIHdl)->http_search_args != nil )
{
(*theCGIHdl)->formFields = CGIFormFieldsFromArgs (
(*theCGIHdl)->http_search_args, &((*theCGIHdl)->totalFields),
&fieldError );
}
break;
#endif
#if kCompileWithCGIpost_args
case HTTP_post :
if ( (*theCGIHdl)->post_args != nil )
{
(*theCGIHdl)->formFields = CGIFormFieldsFromArgs (
(*theCGIHdl)->post_args, &((*theCGIHdl)->totalFields), &fieldError );
}
break;
#endif
}
#endif /* kCompileWithCGIFormHandling && kCompileWithCGIFormAutoProcess && kCompileWithCGImethod */
/* now that the possible need to use them for form fields is over, we can
decode the search args */
#if kCompileWithCGIAutoDecode && kCompileWithCGIhttp_search_args
if ( (*theCGIHdl)->http_search_args != nil )
{
CGIDecodeSpecialChars ( (*theCGIHdl)->http_search_args );
}
#endif
HUnlock ( (Handle)theCGIHdl );
/* this is where the application specific cgi handling comes into play
the function "MyCGIProcess" must be provided by the user of this source code */
MyCGIProcess ( theCGIHdl );
Exit_Complete:
if ( (*theCGIHdl)->thread != nil )
{
// theReply = (*theCGIHdl)->replyEvent;
/* we're in a thread, so we'll need to resume the AppleEvent to have
it complete and return the reply properly */
theErr = AEResumeTheCurrentEvent ( &theAppleEvent, &((*theCGIHdl)->replyEvent), vCGIAEResumeCompleteUPP, (long)theCGIHdl );
/* • kAEUseStandardDispatch ? */
}
else
{
/* need to take care of the AppleEvent reply record */
theErr = cgiAEComplete ( theCGIHdl );
}
/* reset 'quit on idle time' timer */
ResetQuitIdleTimer();
return theErr;
} /* cgiAESearchDocProcess */
/* complete the CGI Apple Event */
pascal OSErr
CGIAEResumeComplete ( const AppleEvent *theAppleEvent, AppleEvent *theReply, long theReference )
{
OSErr theErr;
my_assert ( theReference != nil, "\pCGIAEResumeComplete: the CGI handle (theReference) is nil" );
theErr = cgiAEComplete ( (CGIHdl)theReference );
return theErr;
} /* CGIAEResumeComplete */
/* complete the CGI Apple Event */
static OSErr
cgiAEComplete ( CGIHdl theCGIHdl )
{
OSErr theErr;
HLock ( (Handle)theCGIHdl );
if ( (*theCGIHdl)->responseData != nil )
{
/* if the user's "MyCGIProcess" function set the responseData properly,
return it */
theErr = AEPutParamPtr ( &((*theCGIHdl)->replyEvent), keyDirectObject, typeChar,
(Ptr)((*theCGIHdl)->responseData), (*theCGIHdl)->responseSize );
}
else
{
/* if the user's "MyCGIProcess" failed to set the responseData properly,
return an error header */
theErr = AEPutParamPtr ( &((*theCGIHdl)->replyEvent), keyDirectObject, typeChar,
(Ptr)gHTTPHeaderErr, gHTTPHeaderErrSize );
}
HUnlock ( (Handle)theCGIHdl );
/* deallocate memory */
cgiDisposeHandle ( theCGIHdl );
return theErr;
} /* cgiAEComplete */
#if kCompileWithCGImethod
#pragma segment AppleEvents
/* private function to get an HTTPMethod from an AppleEvent parameter */
static OSErr
cgiAEGetParamHTTPMethod ( const AppleEvent *theAppleEvent, AEKeyword theAEKeyword, HTTPMethod *theMethod, char *tempBuffer, long bufferSize )
{
OSErr theErr;
DescType actualType;
Size actualSize;
int stringDiff;
my_assert ( theMethod != nil, "\pcgiAEGetParamHTTPMethod: theMethod ptr is nil" );
my_assert ( theAppleEvent != nil, "\pcgiAEGetParamHTTPMethod: theAppleEvent ptr is nil" );
my_assert ( tempBuffer != nil, "\pcgiAEGetParamHTTPMethod: tempBuffer ptr is nil" );
theErr = AEGetParamPtr
( theAppleEvent, theAEKeyword, typeChar, &actualType, (Ptr)tempBuffer, bufferSize, &actualSize );
if ( theErr == noErr )
{
my_assert ( actualSize <= bufferSize, "\pcgiAEGetParamHTTPMethod: actual param size too big" );
/* terminate the buffer with a null byte */
tempBuffer[actualSize] = nil;
/* compare the buffer with constants to determine the http method used */
stringDiff = strcmp ( tempBuffer, kCGIHTTPMethodPost );
if ( stringDiff == nil )
{
*theMethod = HTTP_post;
}
else
{
stringDiff = strcmp ( tempBuffer, kCGIHTTPMethodGet );
if ( stringDiff == nil )
{
*theMethod = HTTP_get;
}
else
{
stringDiff = strcmp ( tempBuffer, kCGIHTTPMethodGetConditional );
if ( stringDiff == nil )
{
*theMethod = HTTP_getConditional;
}
else
{
*theMethod = HTTP_UNDEFINED;
}
}
}
}
return theErr;
} /* cgiAEGetParamHTTPMethod */
#endif
/*** SEND PARTIAL SUPPORT ***/
#pragma mark -
/* From the WebSTAR 1.2 addendum:
An ACGI has to inform the WebSTAR server that it intends to
return its results in pieces over a period of time, rather than
simply lumping it all into the value returned from the WWWWsdoc
event. WebSTAR examines the results from an ACGI's reply to see
if it matches the string:
<SEND_PARTIAL>
If this 14 character string is matched exactly (case sensitive),
then WebSTAR doesn’t send anything to the client and keeps the
connection open until the timeout period expires or WebSTAR
receives data via the Send Partial event for that con-nection.
Send Partial events received before the <SEND_PARTIAL> response
to the sdoc
event are also a legal way to indicate that server push
functions are to be performed for a given connection.
As long as Send Partial events are received for a given
connection, the timeout timer is restarted and the data is sent
to the client. If the Send Partial event’s “more” parameter is
FALSE, the server closes the connection and assumes that the
ACGI has finished sending data.
To send events back to WebSTAR from a language like C or Pascal,
you must extract and save the “from” Apple event attribute from
the reply event sent to your WWWWsdoc handler. The “from” event
contains the AEAddressDesc used to address the “SPar” events to
WebSTAR.
Here's the general flow of events
1. A WWW client sends an ACGI URL request to WebSTAR.
2. WebSTAR sends a WWWWsdoc event to the ACGI, passing the
connection ID.
3. The ACGI decides it needs to return data in pieces, so it
replies to the sdoc event with the string <SEND_PARTIAL> and
saves the connection ID.
4. WebSTAR sees that the ACGI wants to return partial data, so
it sets a flag indi-cating that the connection should be checked
periodically for timeouts and resets the timer.
5. The ACGI sends a Send Partial event to WebSTAR with the first
chunk of data, the connection ID, and the “more” flag set to
TRUE, indicating more data to come.
6. WebSTAR handles the Send Partial event, finds the requested
connection, and queues up the data for transmission to the
client (allowing the thread owning the connection to schedule
the transmission.) WebSTAR then resets the time-out for the
connection.
Steps 5 and 6 repeat at whatever interval the client decides
until the “more” parameter is false. WebSTAR receives the final
Send Partial event with the “more” flag set to false, sends the
accompanying data, and closes the client connection. */
/* The connection ID is found in theCGIHdl as the 'connection' field.
Connection is used to identify the particular connection on which to send
the data.
The data to be sent is pointed to by theData, which is dataSize bytes long.
If sendMore is true, there is more data to be sent after processing this
event. If it is false, the server will terminate the connection after
sending the data from this event.
theCGIHdl should not be locked when it is passed to this function.
*/
#if kCompileWithCGISendPartial
#pragma segment AppleEvents
OSErr
CGIAESendPartial ( CGIHdl theCGIHdl, char *theData, long dataSize, Boolean sendMore )
{
OSErr theErr;
DescType descApp;
AEAddressDesc targetAddress;
AppleEvent theAppleEvent;
AppleEvent reply;
short errNum;
DescType returnedType;
long actualSize;
/* ••• need to change this to use the process ID from the server */
/* create the application descriptor */
descApp = kAEClassCGI;
theErr = AECreateDesc ( typeApplSignature, &descApp, sizeof(DescType),
&targetAddress );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
/* create the apple event record */
theErr = AECreateAppleEvent ( kAEClassCGI, kMyAESendPartial, &targetAddress,
kAutoGenerateReturnID, kAnyTransactionID, &theAppleEvent );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
/* put the data */
theErr = AEPutParamPtr ( &theAppleEvent, kCGIPartialData, typeChar, theData, dataSize );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
HLockHi ( (Handle)theCGIHdl );
/* put the connection id */
theErr = AEPutParamPtr ( &theAppleEvent, kConnectionIDKeyword, typeLongInteger,
&((*theCGIHdl)->connection), sizeof(long) );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
HUnlock ( (Handle)theCGIHdl );
/* put the more value */
theErr = AEPutParamPtr ( &theAppleEvent, kMoreKeyword, typeBoolean, &sendMore,
sizeof(Boolean) );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
/* send the apple event */
theErr = AESend ( &theAppleEvent, &reply, kAEWaitReply + kAENeverInteract /*5-14*/,
kAENormalPriority, kAEMyTimeoutInTicks, gAEIdleUPP, nil );
if ( theErr != noErr )
{
goto EXIT_AESEND;
}
/* extract the error reply */
errNum = noErr;
theErr = AEGetParamPtr ( &reply, keyErrorNumber, typeSMInt, &returnedType, &errNum,
sizeof(errNum), &actualSize );
if ( (theErr != noErr) && (theErr != errAEDescNotFound) )
{
goto EXIT_AESEND;
}
theErr = errNum;
EXIT_AESEND:
AEDisposeDesc ( &targetAddress );
AEDisposeDesc ( &theAppleEvent );
return theErr;
} /* CGIAESendPartial */
#endif /* kCompileWithCGISendPartial */
#endif /* kCompileWithCGICode */
/*** EOF ***/